package com.yitong.spiboot;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.net.*;
import java.security.SecureClassLoader;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Pattern;

public class ClassUtil extends SecureClassLoader {

    List<String> classJars=new ArrayList <>(  );
    Map<String,byte[]> cls=new HashMap <>(  );
    Map<String,Class<?>> clss=new HashMap <>(  );
    /**
     *
     * @param jarFilePath
     */
    public ClassUtil(String jarFilePath) {



//
//        // 注意，idea运行此代码报错，原因是idea的启动和java -jar方式的启动不一样
//// 获取当前运行的jar的完整路径
//
////获取到的jar名称是带了路径的，例如/BOOT-INF/lib/a.jar
//
//        jartoclass( jarFilePath );
////        classJars.forEach( e->{
////            jartoclass(jarFilePath+"!/"+e);
////        } );
//        cls.keySet().forEach( e->{
//            try {
//                findClass( e.replace( "/","." ).replace( ".class" ,"") );
//            } catch (ClassNotFoundException e1) {
//                e1.printStackTrace();
//            }
//        } );
////        try {
////            Class <?> aClass = findClass( "com.yitong.spiboot.loader.JarLauncher" );
////            Method main = aClass.getDeclaredMethod( "main", String[].class );
////            main.setAccessible( true );//使私有可获取
////            main.invoke( null,(Object)null );
////        } catch (NoSuchMethodException e) {
////            e.printStackTrace();
////        } catch (IllegalAccessException e) {
////            e.printStackTrace();
////        } catch (InvocationTargetException e) {
////            e.printStackTrace();
////        } catch (ClassNotFoundException e) {
////            e.printStackTrace();
////        }
//        //加载类


    }

    private void jartoclass(String jarFilePath) {
        URI uri = null;
        File file1=null;
        if(jarFilePath.indexOf( "!/" )!=-1){
            try {
                uri = new URI( "jar:file:/"+jarFilePath );
                file1 = new File( uri );
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }
        }else{
            file1 = new File( jarFilePath );
        }


        try (JarFile file = new JarFile( file1 )) {
            Manifest manifestEntry = file.getManifest();

            if (manifestEntry != null) {
                Attributes manifest = manifestEntry.getMainAttributes();

                String attributes = manifest.getValue("Main-Class");
                System.out.println("主属性（main attribute）："+attributes);
                for (Object key : manifest.keySet()) {
                    String value = manifest.getValue((Attributes.Name)key  );
                    System.out.printf("%s=%s\n", key, value);
                }

                Enumeration<JarEntry> entries = file.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
                    classJars.addAll( getDependence(jarFilePath,entry.getName()) );

                }


            } else {
                System.err.println("无法找到清单文件！");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //匹配数字的正则
    private static Pattern pattern = Pattern.compile("[0-9]");




    //获取依赖的jar包名称列表
    private List<String> getDependence(String jarPath,String path){
        List<String> result = new LinkedList<>();
        if(!jarPath.endsWith(".jar")){
            return result;
        }
        String spec;
        URL url;
        try{
            spec = "jar:file:/" + jarPath + "!/" + path;
            url = new URL( spec );
        } catch(MalformedURLException e){
            e.printStackTrace();
            return result;
        }
        try{
            JarURLConnection connection = (JarURLConnection) url.openConnection();
            JarEntry jarFile = connection.getJarEntry();
            String name = jarFile.getName();
            JarFile jarFiles = connection.getJarFile();
            for(Enumeration<JarEntry> ea = jarFiles.entries();ea.hasMoreElements();){
                JarEntry jarEntry = ea.nextElement();
                String name1 = jarEntry.getName();
                System.out.println(name1);
            }
//            System.out.println( name );
            if(name.endsWith( ".class" )){
                byte[] bytes = readStream( url.openStream() );
                String s = name.replaceFirst( "BOOT-INF/classes/", "" );
                cls.put( s,bytes );
                this.loadClass( jarFile.getName() );
            }
            if(name.endsWith( ".jar" )){
//                Attributes attributes = jarFile.getAttributes();
//                JarURLConnection jarURLConnection = (JarURLConnection) new URL( spec ).openConnection();
//                Manifest manifest = jarURLConnection.getManifest();
//                Attributes mainAttributes = manifest.getMainAttributes();
                Manifest manifest = connection.getJarFile().getManifest();

                classJars.add(name  );
            }

        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
    }
    public static byte[] readStream(InputStream inStream) throws Exception{
        ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
        byte[] buff = new byte[100];
        int rc = 0;
        while ((rc = inStream.read(buff, 0, 100)) > 0) {
            swapStream.write(buff, 0, rc);
        }
        byte[] in2b = swapStream.toByteArray();
        return in2b;
    }
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class <?> aClass1 = clss.get( name );
        if(aClass1!=null)return aClass1;
        String concat = name.replace( ".", "/" ).concat( ".class" );
        byte[] buffer = cls.get(concat);
        if(buffer==null)return null;
        try {
            Class <?> aClass = super.defineClass( name, buffer, 0, buffer.length );
            clss.put( name, aClass);
            System.out.println(name);
            return aClass;
        } catch (Error e) {
            e.printStackTrace();
        }
        return super.findClass( name );
    }


    /**
     * Loads the class with the specified <a href="#name">binary name</a>.
     * This method searches for classes in the same manner as the {@link
     * #loadClass(String, boolean)} method.  It is invoked by the Java virtual
     * machine to resolve class references.  Invoking this method is equivalent
     * to invoking {@link #loadClass(String, boolean) <tt>loadClass(name,
     * false)</tt>}.
     *
     * @param name The <a href="#name">binary name</a> of the class
     * @return The resulting <tt>Class</tt> object
     * @throws ClassNotFoundException If the class was not found
     */
    @Override
    public Class <?> loadClass(String name) throws ClassNotFoundException {

        try {
            System.out.println(name);
            Class <?> aClass = findClass( name );
            if(aClass!=null){
                return aClass;
            }
            return super.loadClass( name );
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return this.getClass().getClassLoader().loadClass( name );
    }
}
